//***************************************************************************
// Project:   MDT Wizard Studio
// File:      MainForm.cs
// Purpose:   The main form for editing a Microsoft Deployment Toolkit 
//            wizard XML file.
//
// Version:   3.0.1
//
// Change history:
// 1.0.1  mniehaus  06/25/2008  Cleaned up and commented code for external
//                              release.
// 2.0.0  mniehaus  10/11/2009  Updated for MDT 2010.  Restructured the code
//                              to make it more modular.  Added logic
//                              to display variable values.
// 2.0.2  mniehaus  10/21/2009  Fixed preview logic to display extended
//                              characters (save pane as UTF-8).
//
// 3.0.0  mkoster   11/21/2012  Updated for MDT 2012. Added some new features.
//
// 3.0.1  mkoster   11/22/2012  Code cleanup. 
//
// 3.0.2  mkoster   01/11/2013  Renamed to MDT Wizard Studio
//***************************************************************************

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Xml;
using Alsing.SourceCode;

namespace MDTWizardStudio
{
    /// <summary>
    /// The main form displays the primary user interface.
    /// </summary>
    public partial class MainForm : Form
    {
        #region Instance variables

        private String _wizardDefinitionFile = "";
        private String _definitionDirectory = "";

        private XmlDocument wizard = new XmlDocument();
        private Dictionary<string, XmlDocument> _panes = new Dictionary<string, XmlDocument>();

        private Boolean _isDataReady;
        private Boolean _changed;

        private SyntaxDocument _sDocHTML = new SyntaxDocument();
        private SyntaxDocument _sDocScript = new SyntaxDocument();

        #endregion Instance variables

        #region Constructor

        /// <summary>
        /// Constructor for the main form
        /// </summary>
        public MainForm()
        {
            InitializeComponent();
            listDetail.DisplayMember = "Name";

            // Initialize SyntaxBox for propert syntax highlighting
            try
            {
                _sDocHTML.Folding = false;
                htmlCode.Document = _sDocHTML;
                htmlCode.Document.Parser.Init("XML.syn");

                _sDocScript.Folding = false;
                txtDetail.Document = _sDocScript;
                txtDetail.Document.Parser.Init("VBScript.syn");
            }
            catch (Exception)
            {

                throw;
            }

        }

        #endregion Constructor


        #region Event handlers

        /// <summary>
        /// The MainFormLoad event handler is called after the form has been loaded 
        /// and initialized.  It can then tweak the UI controls as required.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainFormLoad(object sender, EventArgs e)
        {
            ConfigureButtons();
            panelFile.Visible = false;
            panelText.Visible = false;
            panelSettings.Visible = false;
        }


        /// <summary>
        /// The PaneTreeAfterSelect method is called each time a new node is 
        /// selected in the left-hand tree view.  It is responsible for updating
        /// the right-hand panes based on the current selection.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PaneTreeAfterSelect(object sender, TreeViewEventArgs e)
        {
            LoadCode();
            LoadPreview();

            // Hide the detail panels after the pane is changed
            panelFile.Visible = false;
            panelText.Visible = false;

            panelSettings.Visible = (e.Node.Text != "Global");

        }


        /// <summary>
        /// The TabDetailsSelected method is called when any of the tabs is activated.
        /// If the selected tab is the preview tab, it needs to make sure the preview
        /// contains the latest HTML code for the pane.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void TabDetailsSelected(object sender, TabControlEventArgs e)
        {
            if (e.TabPage == tabPreview)
            {
                LoadPreview();
            }
        }

        /// <summary>
        /// The ListDetailSelectedIndexChanged event handler is called when a 
        /// different item is selected in the list on the "Settings" pane.  It
        /// updates the right side of the "Settings" pane based on the current
        /// selection.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ListDetailSelectedIndexChanged(object sender, EventArgs e)
        {
            if (listDetail.SelectedItem == null)
            {
                _isDataReady = false;

                panelFile.Visible = false;
                panelText.Visible = false;
                txtDetail.Document.Text = "";

                _isDataReady = true;

                return;
            }

            var selectedNode = (XmlNode)listDetail.SelectedItem;
            var paneNode = (XmlNode)paneTree.SelectedNode.Tag;

            _isDataReady = false;

            if ((paneNode.Name == "Global" && (selectedNode.Name == "Initialization" || selectedNode.Name == "Validation")) || (selectedNode.Name == "CustomScript"))
            {
                panelFile.Visible = true;
                panelText.Visible = false;
                txtScriptFile.Text = selectedNode.InnerText;

                FileStream newFile = null;

                // Check if file exists. 
                if (!File.Exists(_definitionDirectory + "\\" + txtScriptFile.Text))
                {
                    // Ask user to select or create a new script file
                    saveAsDialog.FileName = "";
                    saveAsDialog.DefaultExt = "vbs";
                    saveAsDialog.Filter = "VBScript Files (*.vbs)|*.vbs";
                    saveAsDialog.OverwritePrompt = false;

                    if (!string.IsNullOrEmpty((_wizardDefinitionFile)))
                        saveAsDialog.InitialDirectory = _definitionDirectory;

                    if (saveAsDialog.ShowDialog() != DialogResult.OK)
                    {
                        return;
                    }

                    txtScriptFile.Text = Path.GetFileName(saveAsDialog.FileName.Trim());

                    // Create new script file if necessary
                    if (!File.Exists(_definitionDirectory + "\\" + txtScriptFile.Text))
                    {

                        using (File.Create(_definitionDirectory + "\\" + txtScriptFile.Text)) { }

                    }

                    // Reset SaveAs properties back to default
                    saveAsDialog.OverwritePrompt = true;
                }

                // Open file and add content to script text area
                var script = File.ReadAllText(_definitionDirectory + "\\" + txtScriptFile.Text);

                txtDetail.Document.Text = script;

                // Show script content
                panelText.Visible = true;

            }
            else if (selectedNode.Name.StartsWith("Button", StringComparison.OrdinalIgnoreCase))
            {
                panelFile.Visible = false;
                panelText.Visible = true;
                txtDetail.Document.Text = selectedNode.InnerXml;
            }
            else
            {
                panelFile.Visible = false;
                panelText.Visible = true;
                txtDetail.Document.Text = selectedNode.InnerText;
            }

            _isDataReady = true;
        }


        /// <summary>
        /// The AddSettingClick event handler is called when the user clicks on the
        /// "Add" entry in the settings context menu or on the "Add" button.
        /// The ChooseType form is used to specify the details of the item to add.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AddSettingClick(object sender, EventArgs e)
        {
            // Get current node type
            var selectedNode = (XmlNode)paneTree.SelectedNode.Tag;

            var newEntry = new ChooseType(selectedNode.Name);
            if (newEntry.ShowDialog() == DialogResult.OK)
            {
                AddSetting(newEntry.SelectedType);
            }
        }


        /// <summary>
        /// The RemoveSettingClick event handler is called when the user clicks on the
        /// "Remove" entry in the settings context menu or on the "Remove" button.
        /// It removes the currently selected setting from the list of settings.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RemoveSettingClick(object sender, EventArgs e)
        {
            if (listDetail.SelectedItem == null)
                return;

            // TODO: Ask for confirmation

            var selectedNode = (XmlNode)listDetail.SelectedItem;
            selectedNode.ParentNode.RemoveChild(selectedNode);
            listDetail.Items.Remove(listDetail.SelectedItem);
            txtDetail.Document.Text = "";
            if (listDetail.Items.Count > 0)
                listDetail.SelectedItem = 0;
            _changed = true;
        }


        /// <summary>
        /// The AddPaneClick event handler is called when the user clicks on the
        /// "Add" entry in the form or panes context menu or on the "Add" button.
        /// It uses the ChooseID dialog to request a new name, creates a new
        /// pane and adds it to the list of panes.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AddPaneClick(object sender, EventArgs e)
        {
            // Prompt for the pane ID
            var newPaneID = new ChooseId { FilenameEnabled = true };

            if (newPaneID.ShowDialog() != DialogResult.OK)
                return;

            // Ensure the ID is unique
            if (!CheckPaneIDUnique(newPaneID.PaneId))
            {
                MessageBox.Show("Pane ID already in use. Please specify a unique ID!",
                "ID not unique",
                MessageBoxButtons.OK,
                MessageBoxIcon.Error);

                return;
            }

            // Add the new pane to the wizard definition
            string referenceName = CreateNewPaneFile(newPaneID.PaneId, newPaneID.Filename);
            AddPaneToDefinition(newPaneID.PaneId, referenceName);

            _changed = true;
        }


        /// <summary>
        /// The RemovePaneClick event handler is called when the user clicks on the
        /// "Remove" entry in the form or panes context menu or on the "Remove" button.
        /// It removes the currently selected pane from the list of panes.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RemovePaneClick(object sender, EventArgs e)
        {
            // Is a pane currently selected?  If not, return.
            if (paneTree.SelectedNode == null)
                return;

            // TODO: Ask for confirmation

            var selectedNode = (XmlNode)paneTree.SelectedNode.Tag;
            selectedNode.ParentNode.RemoveChild(selectedNode);
            paneTree.Nodes.Remove(paneTree.SelectedNode);
            if (paneTree.Nodes.Count > 0)
                paneTree.SelectedNode = paneTree.Nodes[0];

            // remove from _panes dictionary as well if necessary
            if (selectedNode.Attributes["id"] != null)
                _panes.Remove(selectedNode.Attributes["id"].Value);

            _changed = true;
        }


        /// <summary>
        /// The EditClick method will open the specified script file in Notepad for the user to edit.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void EditClick(object sender, EventArgs e)
        {
            // File specified?  If not, return.

            if (String.IsNullOrEmpty(txtScriptFile.Text.Trim()))
                return;

            // Start a process to run Notepad.exe, passing it the name of the file to open.

            var wizProcess = new Process();
            var startInfo = new ProcessStartInfo("notepad.exe", "\"" + _definitionDirectory + @"\" + txtScriptFile.Text.Trim() + "\"")
                                {
                                    WindowStyle = ProcessWindowStyle.Normal,
                                    UseShellExecute = false
                                };
            wizProcess.StartInfo = startInfo;
            wizProcess.Start();
        }


        /// <summary>
        /// The HtmlCodeTextChanged event is fired any time the user changes the text in the HTML textbox.
        /// As a result of this event, we need to save the text back into the appropriate XML node.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void HtmlCodeTextChanged(object sender, EventArgs e)
        {
            XmlDocument referencedPane;

            if (paneTree.SelectedNode == null)
                return;

            var selectedPane = (XmlNode)paneTree.SelectedNode.Tag;


            // Check if current pane definition is in a referenced file
            if (selectedPane.Attributes["reference"] != null)
            {
                referencedPane = _panes[selectedPane.Attributes["id"].Value];
                selectedPane = referencedPane.SelectSingleNode("//Pane");


                // Update the Body node with the current HTML text.

                referencedPane.SelectSingleNode("//Pane/Body").InnerXml = referencedPane.CreateCDataSection(htmlCode.Document.Text).OuterXml;
            }
            else
            {
                // Global node selected?  If so, it has no HTML so return.
                if (selectedPane.Name == "Global")
                    return;

                // Update the Body node with the current HTML text.
                selectedPane.SelectSingleNode("Body").InnerXml = wizard.CreateCDataSection(htmlCode.Document.Text).OuterXml;
            }

            if (_isDataReady)
                _changed = true;
        }


        /// <summary>
        /// The ScriptFileChanged event is fired when the name of the script is changed.
        /// As a result of the change, we need to save the new file name back into the XML node.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ScriptFileChanged(object sender, EventArgs e)
        {
            var selectedNode = (XmlNode)listDetail.SelectedItem;
            if (selectedNode == null)
                return;

            // Update the current XML node with the new text from the form.
            selectedNode.InnerText = txtScriptFile.Text;
            if (_isDataReady)
                _changed = true;
        }


        /// <summary>
        /// The DetailChanged event is fired when the detail for a button or
        /// a custom statement is changed.  As a result of the change, the updated
        /// text needs to be saved back into the XML node.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DetailChanged(object sender, EventArgs e)
        {
            var selectedNode = (XmlNode)listDetail.SelectedItem;
            if (selectedNode == null)
                return;

            // Update the appropriate node.  If it is a button, we need
            // to update the inner XML, otherwise we change the inner text.
            if ((paneTree.SelectedNode.Name == "Global" && (selectedNode.Name == "Initialization" || selectedNode.Name == "Validation")) || (selectedNode.Name == "CustomScript"))
            {
                // Save content to referenced script file.
                try
                {
                    File.WriteAllText(_definitionDirectory + "\\" + txtScriptFile.Text, txtDetail.Document.Text);
                }
                catch (Exception)
                {

                    throw;
                }

            }
            else if (selectedNode.Name.StartsWith("Button", StringComparison.OrdinalIgnoreCase))
                selectedNode.InnerXml = txtDetail.Document.Text;
            else
            {    // Get selected pane
                var selectedPane = (XmlNode)paneTree.SelectedNode.Tag;

                if (selectedPane.Attributes["reference"] == null)
                {
                    selectedNode.InnerXml = wizard.CreateCDataSection(txtDetail.Document.Text).OuterXml;
                }
                else
                {
                    // If it's a refernced pane, we need to have the pane xml document create the CData section
                    var pane = _panes[selectedPane.Attributes["id"].Value];

                    selectedNode.InnerXml = pane.CreateCDataSection(txtDetail.Document.Text).OuterXml;
                }
            }

            if (_isDataReady)
                _changed = true;
        }


        /// <summary>
        /// The TitleChanged event is fired when the text for the title
        /// is changed. As a result of the change, the updated title needs to
        /// be saved back in to the definition or referenced xml file.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TitleChanged(object sender, EventArgs e)
        {
            XmlDocument referencedPane;
            var selectedPane = (XmlNode)paneTree.SelectedNode.Tag;

            // Pane selected?  If not, return.
            if (selectedPane == null)
                return;

            // Check if current pane definition is in a referenced file
            if (selectedPane.Attributes["reference"] != null)
            {
                referencedPane = _panes[selectedPane.Attributes["id"].Value];
                selectedPane = referencedPane.SelectSingleNode("//Pane");
            }
            else
            {
                referencedPane = wizard;
            }

            // if title is empty, remove the attribute
            if (string.IsNullOrEmpty(txtTitle.Text))
            {
                if (selectedPane.Attributes["title"] != null)
                {
                    var titleAttribute = selectedPane.Attributes["title"];
                    selectedPane.Attributes.Remove(titleAttribute);
                }
            }
            else // Update the title attribute
            {
                if (selectedPane.Attributes["title"] == null)
                {
                    var titleAttribute = referencedPane.CreateAttribute("title");
                    titleAttribute.Value = txtTitle.Text;
                    selectedPane.Attributes.SetNamedItem(titleAttribute);
                }
                else
                {
                    selectedPane.Attributes["title"].Value = txtTitle.Text;
                }
            }

            if (_isDataReady)
                _changed = true;
        }


        /// <summary>
        /// The PaneTreeItemDrag event handler is called when an item
        /// is dragged from the Panes tree.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PaneTreeItemDrag(object sender, ItemDragEventArgs e)
        {
            DoDragDrop(e.Item, DragDropEffects.Move);
        }


        /// <summary>
        /// The PaneTreeDragEnter event handler is called when an item 
        /// is dragged from the Panes tree.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PaneTreeDragEnter(object sender, DragEventArgs e)
        {
            e.Effect = e.Data.GetDataPresent("System.Windows.Forms.TreeNode", true) ? DragDropEffects.Move : DragDropEffects.None;
        }


        /// <summary>
        /// The PaneTreeDragOver event handler is called when an item is
        /// dragged over a node.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PaneTreeDragOver(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent("System.Windows.Forms.TreeNode", true))
            {
                // Get target node
                Point pt = paneTree.PointToClient(new Point(e.X, e.Y));
                TreeNode destinationNode = paneTree.GetNodeAt(pt);


                // Check if target node is currently selected
                // If so no need to validate again
                if (paneTree.SelectedNode != destinationNode)
                {
                    // Select the node currently under the cursor
                    paneTree.SelectedNode = destinationNode;

                    e.Effect = DragDropEffects.Move;
                }
            }
        }


        /// <summary>
        /// The PaneTreeDragDrop event handler is called when an item
        /// is dropped on the Panes tree.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PaneTreeDragDrop(object sender, DragEventArgs e)
        {
            TreeNode newNode;

            if (e.Data.GetDataPresent("System.Windows.Forms.TreeNode", true))
            {
                // Get target node
                var pt = paneTree.PointToClient(new Point(e.X, e.Y));
                var destinationNode = paneTree.GetNodeAt(pt);


                newNode = (TreeNode)e.Data.GetData("System.Windows.Forms.TreeNode");
                if (destinationNode.TreeView == newNode.TreeView)
                {
                    // Move node in Treeview
                    newNode.Remove();

                    int index;

                    if (destinationNode == paneTree.Nodes[0])
                        index = 0;
                    else
                        index = destinationNode.Index + 1;

                    paneTree.Nodes[0].Nodes.Insert(index, newNode);
                    paneTree.SelectedNode = newNode;


                    // Update XML
                    foreach (TreeNode currentTreeNode in paneTree.Nodes[0].Nodes)
                    {
                        var newNodeXml = (XmlNode)currentTreeNode.Tag;
                        wizard.SelectSingleNode("//Wizard").AppendChild(newNodeXml);
                    }
                }
            }

        }


        /// <summary>
        /// The ChkSeparateCheckedChanged event handler is called when a user
        /// clicks on the "Separate file" check box, if the current pane is
        /// still in the definition file.
        /// It will use ChooseID to prompt for the new filename, create a new
        /// file based on this name and linked it properly to the definition file.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ChkSeparateCheckedChanged(object sender, EventArgs e)
        {

            var selectedPane = (XmlNode)paneTree.SelectedNode.Tag;

            // Verify that this isn't a referenced file already
            if (selectedPane.Attributes["reference"] != null) return;

            var paneID = selectedPane.Attributes["id"].Value;

            // Prepare a new filename
            string fileName = CreateUniqueFileName(paneID, "");

            // prompt user for filename
            // changes to Pane id will be disabled
            var newFileName = new ChooseId
                                  {
                                      PaneId = paneID,
                                      PaneIDEnabled = false,
                                      Filename = fileName,
                                      FilenameEnabled = true
                                  };

            if (newFileName.ShowDialog() != DialogResult.OK)
                return;

            if (!string.IsNullOrEmpty(newFileName.Filename))
            {
                fileName = CreateUniqueFileName(paneID, newFileName.Filename);
            }

            // Create a new pane file
            fileName = CreateNewPaneFile(paneID, fileName);

            // Get new pane file object
            var newPane = _panes[paneID];


            // Copy original information to new pane

            foreach (XmlNode paneNode in selectedPane.SelectNodes("*"))
            {
                if (paneNode.Name == "Body")
                {
                    newPane.SelectSingleNode("//Body").InnerXml = paneNode.InnerXml;
                }
                else
                {
                    XmlNode newPaneNode = newPane.CreateElement(paneNode.Name);
                    newPaneNode.InnerXml = paneNode.InnerXml;
                    newPane.SelectSingleNode("//Pane").AppendChild(newPaneNode);

                }
                //selectedPane.re
                // Remove original Node
                selectedPane.RemoveChild(paneNode);
            }


            if (selectedPane.Attributes["title"] != null)
            {
                var titleAttribute = newPane.CreateAttribute("title");
                titleAttribute.Value = selectedPane.Attributes["title"].Value;
                newPane.SelectSingleNode("//Pane").Attributes.SetNamedItem(titleAttribute);

                selectedPane.Attributes.RemoveNamedItem("title");
            }

            // Update definition file
            var referenceAttribute = wizard.CreateAttribute("reference");
            referenceAttribute.Value = fileName;
            selectedPane.Attributes.SetNamedItem(referenceAttribute);


            // Save changes in reference file 
            newPane.Save(_definitionDirectory + "\\" + fileName);

            // reload selected pane
            LoadCode();

            _changed = true;
        }


        /// <summary>
        /// The ChkNavigationCheckedChanged event handler is called when a user
        /// clicks on the "Hide Navigation" checkbox of the global node.
        /// It adds or removes the HideNavigation child node on the global node.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ChkNavigationCheckedChanged(object sender, EventArgs e)
        {
            if (paneTree.SelectedNode.Text != "Global" || !_isDataReady) return;
            var selectedPane = (XmlNode)paneTree.SelectedNode.Tag;

            if (chkNavigation.Checked)
            {
                XmlNode newNode = wizard.CreateElement("HideNavigation");

                newNode.InnerText = "true";
                selectedPane.AppendChild(newNode);

            }
            else
            {
                var navigationNode = selectedPane.SelectSingleNode("HideNavigation");

                if (navigationNode != null)
                {
                    selectedPane.RemoveChild(navigationNode);
                }
            }
        }

        /// <summary>
        /// The HideButtonsCheckedChanged event handler is called if a user
        /// checks the "HideButtons" checkbox for a specific pane.
        /// If checked it will add a "HideBackNext" Element to the body element of the pane.
        /// If unchecked again, it will set this element to "false", but keep it.
        /// This way the Buttons can be hidden on one pane and shown on another pane again.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void HideButtonsCheckedChanged(object sender, EventArgs e)
        {
            // handle only after a pane has been selected
            if (paneTree.SelectedNode.Text == "Global" || !_isDataReady || paneTree.SelectedNode.Tag == null) return;

            // Get selected pane
            var selectedPane = (XmlNode)paneTree.SelectedNode.Tag;
            var id = selectedPane.Attributes["id"].Value;

            // Get referenced pane if necessary
            var isReference = false;
            if (selectedPane.Attributes["reference"] != null)
            {
                selectedPane = _panes[id].SelectSingleNode("/Wizard/Pane[@id='" + id + "']");
                isReference = true;
            }

            if (selectedPane == null) return;

            // Create Node if necessary
            var hideBackNextNode = selectedPane.SelectSingleNode("HideBackNext");

            if (hideBackNextNode == null)
            {
                // Create new node based on wizard or pane
                hideBackNextNode = isReference
                                       ? _panes[id].CreateElement("HideBackNext")
                                       : wizard.CreateElement("HideBackNext");

                // Add node to pane
                selectedPane.AppendChild(hideBackNextNode);
            }

            if (chkHideButtons.Checked)
            {
                hideBackNextNode.InnerText = "true";
            }
            else
            {
                hideBackNextNode.InnerText = "false";
            }
            hideBackNextNode.InnerText = (chkHideButtons.Checked ? "true" : "false");
        }

        #region Menu event handlers


        /// <summary>
        /// The NewClick event handler menu is called when the user
        /// chooses the "New" menu item. It opens a SaveAs dialog to allow the user 
        /// to create a new xml file. 
        /// It will create a base XML file and then call the Load method to read this
        /// new XML file.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void NewClick(object sender, EventArgs e)
        {
            // Tell the user if there are pending changes that need to be saved first.
            if (_changed)
            {
                var saveResult = MessageBox.Show("Save changes made to current wizard definition XML file?",
                                                 "Save changes",
                                                 MessageBoxButtons.YesNoCancel,
                                                 MessageBoxIcon.Question,
                                                 MessageBoxDefaultButton.Button1,
                                                 MessageBoxOptions.DefaultDesktopOnly);

                switch (saveResult)
                {
                    case DialogResult.Yes:
                        SaveConfigurationFile();
                        //wizard.Save(_wizardDefinitionFile);
                        //_changed = false;
                        break;
                    case DialogResult.No:

                        break;
                    case DialogResult.Cancel:
                        return;
                }
            }


            saveAsDialog.DefaultExt = "xml";
            saveAsDialog.Filter = "XML Files (*.xml)|*.xml";

            if (!string.IsNullOrEmpty((_wizardDefinitionFile)))
                saveAsDialog.InitialDirectory = _definitionDirectory;

            if (saveAsDialog.ShowDialog() != DialogResult.OK)
                return;

            var fileNameFull = saveAsDialog.FileName.Trim();
            const string paneID = "Pane1";
            _wizardDefinitionFile = fileNameFull;
            _definitionDirectory = Directory.GetParent(_wizardDefinitionFile).FullName;


            // Create an initial pane based on the definition file name
            var newPaneFileName = CreateNewPaneFile(paneID, "");

            // Create base XML file
            var nl = Environment.NewLine;

            var newWizardXml = @"<Wizard>" + nl;
            newWizardXml += @"    <Global>" + nl;
            newWizardXml += @"    </Global>" + nl;
            newWizardXml += @"    <Pane id=""" + paneID + @""" reference=""" + newPaneFileName + @""">" + nl;
            newWizardXml += @"    </Pane>" + nl;
            newWizardXml += @"</Wizard>";

            var newWizard = new XmlDocument();
            newWizard.LoadXml(newWizardXml);

            //TODO: propert indent of XML file

            // Save definition file
            newWizard.Save(_wizardDefinitionFile);


            //Load configuration
            LoadConfigurationFile(_wizardDefinitionFile);

        }

        /// <summary>
        /// The OpenClick event handler menu is called when the user
        /// chooses the "Open" menu item.  It presents a file dialog to allow the
        /// user to select an XML file, then validates that the XML file is indeed a 
        /// wizard XML file.  After validation, it reads the XML file and populates the
        /// left-hand pane.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OpenClick(object sender, EventArgs e)
        {
            // Select the file
            if (wizardFileDialog.ShowDialog() != DialogResult.OK)
                return;

            // Load the file
            LoadConfigurationFile(wizardFileDialog.FileName.Trim());

        }


        /// <summary>
        /// The SaveClick method saves the currently-open definition file.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SaveClick(object sender, EventArgs e)
        {
            SaveConfigurationFile();
        }


        /// <summary>
        /// The SaveAsClick method saves the currently-open definition file with a new name.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SaveAsClick(object sender, EventArgs e)
        {
            saveAsDialog.InitialDirectory = _definitionDirectory;
            saveAsDialog.FileName = _wizardDefinitionFile;
            if (saveAsDialog.ShowDialog() != DialogResult.OK)
                return;

            //wizard.Save(saveAsDialog.FileName);
            _wizardDefinitionFile = saveAsDialog.FileName;
            _definitionDirectory = Directory.GetParent(_wizardDefinitionFile).FullName;
            SaveConfigurationFile();
        }


        /// <summary>
        /// The ExitClick event handler method is called when the user chooses the "Exit"
        /// menu item. 
        /// It checks if there are any unsaved changes and prompts the user if they shall
        /// be changed.
        /// Then closes the form (and therefore, the application).
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ExitClick(object sender, EventArgs e)
        {
            // Tell the user if there are pending changes that need to be saved first.
            if (_changed)
            {
                DialogResult result =
                    MessageBox.Show(
                        "Do you want to save the changes that were made to the wizard definition XML file?",
                        "Unsaved changes.",
                        MessageBoxButtons.YesNoCancel, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1,
                        MessageBoxOptions.DefaultDesktopOnly);

                switch (result)
                {
                    case DialogResult.Yes:
                        SaveConfigurationFile();
                        break;

                    case DialogResult.No:
                        break;

                    case DialogResult.Cancel:
                        return;
                }
            }

            // Close the form
            Close();
        }


        /// <summary>
        /// The TestClick event handler is called when the user clicks on the
        /// "Test" entry in the form menu.
        /// It shows the Variables form, that runs gather, shows the discovered
        /// values and lets the user update them for testing purposes. Then shows
        /// the wizard based on the current definition file.
        /// After the wizard has completed, it will show again all variables with
        /// the former and updated values.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TestClick(object sender, EventArgs e)
        {
            // XML file currently open?  If not, return.
            if (String.IsNullOrEmpty(_wizardDefinitionFile.Trim()))
                return;

            // Tell the user if there are pending changes that need to be saved first.
            if (_changed)
            {
                MessageBox.Show("Please save the changes that were made to the wizard definition XML file before testing the wizard.", "Unable to test until saved",
                    MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly);
                return;
            }

            // Display a form with the variable values, allowing the user to override anything they want.
            var variableForm = new Variables { DefinitionFile = _wizardDefinitionFile.Trim() };
            variableForm.ShowDialog();
        }


        private void PaneTreeNodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                paneTree.SelectedNode = e.Node;
            }
        }


        /// <summary>
        /// The CopyPaneClick event handler is called when a user clicks on
        /// the "Copy" entry in the form or pane context menu.
        /// The method calls CopyPane to store a copy of the currently selected 
        /// pane in the Clipboard.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CopyPaneClick(object sender, EventArgs e)
        {
            // Nothing to copy if no node is selected
            if (paneTree.SelectedNode == null)
                return;

            // Nothing to copy if the root node is selected
            if (paneTree.SelectedNode == paneTree.Nodes[0])
            {
                Console.Beep();
                return;
            }

            // Put the XML on the clipboard
            Clipboard.SetData("WizardPane", ((XmlNode)paneTree.SelectedNode.Tag).OuterXml);
        }


        /// <summary>
        /// The PastePaneClick event handler is called when a user clicks on 
        /// the "Copy" entry in the form menu or the panes contet menu.
        /// It pastes a pane from the clipboard if available.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PastePaneClick(object sender, EventArgs e)
        {
            String nodeXml;

            // If the clipboard doesn't contain a wizard pane, then there's nothing to paste so return
            if (Clipboard.ContainsData("WizardPane"))
            {
                // Get the XML string for the pane
                nodeXml = (String)Clipboard.GetData("WizardPane");
            }
            else if (Clipboard.ContainsData("Text"))
            {
                String clipboardText = Clipboard.GetText(TextDataFormat.UnicodeText).Trim();
                if (clipboardText.StartsWith("<Pane", StringComparison.OrdinalIgnoreCase) && clipboardText.EndsWith("</Pane>", StringComparison.OrdinalIgnoreCase))
                    nodeXml = clipboardText;
                else
                {
                    Console.Beep();
                    return;
                }
            }
            else
            {
                Console.Beep();
                return;
            }


            // Prompt for the pane ID
            var newPaneID = new ChooseId();
            if (newPaneID.ShowDialog() != DialogResult.OK)
                return;

            // Ensure the ID is unique
            foreach (XmlNode currentPane in wizard.SelectNodes("//Pane"))
            {
                if (currentPane.Attributes["id"].Value == newPaneID.PaneId)
                {
                    MessageBox.Show("Pane ID already in use. Please specify a unique ID!",
                                    "ID not unique",
                                    MessageBoxButtons.OK,
                                    MessageBoxIcon.Error);
                    return;
                }
            }


            // Create a new pane document and store the "old" id
            string oldID;
            var newPaneDoc = new XmlDocument();
            try
            {
                newPaneDoc.LoadXml(nodeXml);

                // Keep old ID
                if (newPaneDoc.SelectSingleNode("//Pane").Attributes["id"] != null)
                    oldID = newPaneDoc.SelectSingleNode("//Pane").Attributes["id"].Value;

                // Update ID
                newPaneDoc.SelectSingleNode("//Pane").Attributes["id"].Value = newPaneID.PaneId;

            }
            catch (XmlException ex)
            {
                MessageBox.Show(ex.Message + "\n\n" + nodeXml, "Unable to paste node", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, 0);
                return;
            }

            // Append the node after the current selection
            XmlNode newPane;
            if (paneTree.SelectedNode == null || paneTree.SelectedNode == paneTree.Nodes[0])
            {
                newPane = wizard.DocumentElement.InsertAfter(wizard.ImportNode(newPaneDoc.DocumentElement, true), (XmlNode)paneTree.Nodes[0].Tag);
            }
            else
            {
                newPane = wizard.DocumentElement.InsertAfter(wizard.ImportNode(newPaneDoc.DocumentElement, true), (XmlNode)paneTree.SelectedNode.Tag);
            }


            // If copied pane is stored in a referenced file, copy the file 
            // and update the reference information in the definition file
            if (newPane.Attributes["reference"] != null)
            {
                var newPaneFileName = CreateUniqueFileName(newPaneID.PaneId, "");

                File.Move(_definitionDirectory + "\\" + newPane.Attributes["reference"].Value,
                          _definitionDirectory + "\\" + newPaneFileName);
                //newPaneFileName = CopyPaneFile(newPane.Attributes["reference"].Value, newPaneFileName);

                newPane.Attributes["reference"].Value = newPaneFileName;

                // Load new reference
                var newReferencePane = new XmlDocument();
                newReferencePane.Load(_definitionDirectory + "\\" + newPaneFileName);

                // Update ID attribute, as reference has just been copied
                newReferencePane.SelectSingleNode("//Pane").Attributes["id"].Value = newPaneID.PaneId;

                // Save change to reference
                newReferencePane.Save(_definitionDirectory + "\\" + newPaneFileName);

                // Add referenced file to panes dictionary
                _panes.Add(newPaneID.PaneId, newReferencePane);
            }


            // Add the new pane to the tree
            var newNode = new TreeNode(newPane.Attributes.GetNamedItem("id").Value) { Tag = newPane };

            if (paneTree.SelectedNode == null || paneTree.SelectedNode == paneTree.Nodes[0])
                paneTree.Nodes[0].Nodes.Insert(0, newNode);
            else
                paneTree.Nodes[0].Nodes.Insert(paneTree.SelectedNode.Index + 1, newNode);

            _changed = true;
        }



        /// <summary>
        /// The RenamePaneClick event handler is called when a user clicks
        /// on the "Rename" entry in the form menu or panes context menu. 
        /// It uses the ChooseID dialog to ask for a new pane id and optionally
        /// a new filename and then updates the pane and filename. 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RenamePaneClick(object sender, EventArgs e)
        {
            // Get current ID
            var selectedNode = (XmlNode)paneTree.SelectedNode.Tag;

            // Prompt for the pane ID
            var newPaneID = new ChooseId();
            var currentID = selectedNode.Attributes["id"].Value;
            newPaneID.PaneId = currentID;
            if (selectedNode.Attributes["reference"] != null)
            {
                newPaneID.Filename = selectedNode.Attributes["reference"].Value;
                newPaneID.FilenameEnabled = true;
            }
            else
            {
                newPaneID.Filename = "";
                newPaneID.FilenameEnabled = false;
            }


            if (newPaneID.ShowDialog() != DialogResult.OK)
                return;

            // Evaluate some information about what changed
            var paneIDChanged = ((!String.IsNullOrEmpty(newPaneID.PaneId)) && (newPaneID.PaneId != currentID));
            var referencedFile = (selectedNode.Attributes["reference"] != null);
            var fileNameChanged = false;
            var oldRef = string.Empty;
            var newRef = string.Empty;

            if (referencedFile)
            {
                oldRef = selectedNode.Attributes["reference"].Value;
                newRef = newPaneID.Filename;
                fileNameChanged = ((!string.IsNullOrEmpty(newPaneID.Filename)) && (oldRef != newRef));
            }


            // change id and filename if required
            if (paneIDChanged || fileNameChanged)
            {
                // Ensure the ID is unique
                if (paneIDChanged && !CheckPaneIDUnique(newPaneID.PaneId))
                {
                    MessageBox.Show("Pane ID already in use. Please specify a unique ID!",
                                    "ID not unique",
                                    MessageBoxButtons.OK,
                                    MessageBoxIcon.Error);
                    return;
                }

                if (paneIDChanged)
                {
                    // change id of pane in definition file
                    selectedNode.Attributes["id"].Value = newPaneID.PaneId;
                    paneTree.SelectedNode.Name = newPaneID.PaneId;
                    paneTree.SelectedNode.Text = newPaneID.PaneId;
                }

                // update key and id
                if (referencedFile)
                {
                    if (fileNameChanged)
                    {
                        // Rename file
                        try
                        {
                            // Ensure to rename the file only, if the new filename doesn't already exist
                            if (!File.Exists(_definitionDirectory + "\\" + newRef))
                            {
                                // Rename file by "moving" it
                                File.Move(_definitionDirectory + "\\" + oldRef, _definitionDirectory + "\\" + newRef);

                                // Update reference
                                selectedNode.Attributes["reference"].Value = newRef;
                                txtFilename.Text = newRef;
                            }
                        }
                        catch (Exception)
                        {
                            MessageBox.Show("Unable to rename referenced pane file!",
                                            "Rename file.",
                                            MessageBoxButtons.OK,
                                            MessageBoxIcon.Error);
                        }
                    }

                    if (paneIDChanged)
                    {
                        var tempNode = _panes[currentID];
                        _panes.Remove(currentID);
                        tempNode.SelectSingleNode("/Wizard/Pane[@id='" + currentID + "']").Attributes["id"].Value = newPaneID.PaneId;
                        _panes.Add(newPaneID.PaneId, tempNode);
                    }
                }

            }
        }


        /// <summary>
        /// The ImportPaneClick event handler is called when a user
        /// clicks on the "Import" entry on the form menu or panes context menu.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ImportPaneClick(object sender, EventArgs e)
        {
            // Select the file to import
            if (wizardFileDialog.ShowDialog() != DialogResult.OK)
                return;

            // Store selected values
            string selectedDirectory = Path.GetDirectoryName(wizardFileDialog.FileName.Trim());
            string selectedFileName = Path.GetFileName(wizardFileDialog.FileName.Trim());


            // Load selected file and ensure it's a definition file
            var newPane = new XmlDocument();
            newPane.Load(wizardFileDialog.FileName.Trim());

            if (newPane.SelectNodes("//Pane").Count == 0)
            {
                MessageBox.Show("Invalid definition XML file selected, no panes defined.", "Invalid File",
                                MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1,
                                MessageBoxOptions.DefaultDesktopOnly);
                return;
            }
            if (newPane.SelectNodes("//Pane").Count > 1)
            {
                MessageBox.Show("Invalid definition XML file selected, only single panes can be imported.",
                                "Invalid File",
                                MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1,
                                MessageBoxOptions.DefaultDesktopOnly);
                return;
            }


            // Copy the file to current definition directoy if necessary
            if (selectedDirectory != _definitionDirectory)
            {
                // Check if file exists already
                if (!File.Exists(_definitionDirectory + "\\" + selectedFileName))
                    File.Copy(wizardFileDialog.FileName.Trim(), _definitionDirectory + "\\" + selectedFileName);

                // Copy all referenced script files from the global section as well
                foreach (
                    XmlNode customScript in
                        newPane.SelectNodes(
                            "/Wizard/Global/CustomScript|/Wizard/Global/Initialization|/Wizard/Global/Validation"))
                {
                    if (!string.IsNullOrEmpty(customScript.InnerText))
                    {
                        try
                        {
                            if (!File.Exists(_definitionDirectory + "\\" + customScript.InnerText))
                                File.Copy(selectedDirectory + "\\" + customScript.InnerText,
                                          _definitionDirectory + "\\" + customScript.InnerText);
                        }
                        catch (Exception)
                        {
                            MessageBox.Show(
                                string.Format("Unable to copy file {0} from {1} to {2}.", customScript.InnerText,
                                              selectedDirectory, _definitionDirectory),
                                "Error",
                                MessageBoxButtons.OK,
                                MessageBoxIcon.Error);
                        }
                    }
                }

                // Copy all files that are referenced in the Body element
                // We handle files in the same folder only
                // TODO: Enable subfolders

                foreach (XmlNode bodyNode in newPane.SelectNodes("/Wizard/Pane/Body"))
                {
                    var pattern = @"(\S+)=[""']?((?:.(?![""']?\s+(?:\S+)=|[>""']))+.)[""']?";
                    var refTags = new Regex(pattern, RegexOptions.None);

                    foreach (Match refTag in refTags.Matches(bodyNode.InnerText))
                    {
                        if (refTag.Groups[1].Value == "src")
                        {
                            var filename = refTag.Groups[2].Value;

                            // Check if file exists
                            if (File.Exists(selectedDirectory + "\\" + filename))
                            {
                                // Check if file does not exist at destination
                                if (!File.Exists(_definitionDirectory + "\\" + filename))
                                {
                                    File.Copy(selectedDirectory + "\\" + filename, _definitionDirectory + "\\" + filename);
                                }
                            }
                        }
                    }
                }
            }


            // Ensure PanedID is unique
            var paneID = string.Empty;
            if (newPane.SelectSingleNode("//Pane").Attributes["id"] != null)
                paneID = newPane.SelectSingleNode("//Pane").Attributes["id"].Value;

            while (((string.IsNullOrEmpty(paneID)) || (!CheckPaneIDUnique(paneID))))
            {
                // Prompt for the pane ID
                var newPaneID = new ChooseId { PaneId = paneID };
                newPaneID.ShowDialog();
                newPaneID.Filename = selectedFileName;
                newPaneID.FilenameEnabled = false;
                paneID = newPaneID.PaneId;

            }

            // Update ID
            var idAttribute = newPane.CreateAttribute("title");
            idAttribute.Value = paneID;
            newPane.SelectSingleNode("//Pane").Attributes.SetNamedItem(idAttribute);

            // Save changes to new pane
            newPane.Save(_definitionDirectory + "\\" + selectedFileName);

            // Add pane to _Panes dictionary
            _panes.Add(paneID, newPane);

            // Add the new pane to definition
            AddPaneToDefinition(paneID, selectedFileName);


            // Select newly imported pane
            paneTree.SelectedNode = paneTree.SelectedNode.NextNode;

            _changed = true;
        }


        /// <summary>
        /// The DetailsContextMenuOpening event handler is called when a user
        /// right-clicks on the settings area.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DetailsContextMenuOpening(object sender, System.ComponentModel.CancelEventArgs e)
        {

            if (paneTree.SelectedNode == null)
                return;

            var contxtMenu = (ToolStripMenuItem)detailsContextMenu.Items[0];

            if (paneTree.SelectedNode.Text == "Global")
            {
                contxtMenu.DropDownItems["customScriptToolStripMenuItem"].Enabled = true;
                contxtMenu.DropDownItems["initializationToolStripMenuItem"].Enabled = true;
                contxtMenu.DropDownItems["initializationCloseoutToolStripMenuItem"].Enabled = false;
                contxtMenu.DropDownItems["validationToolStripMenuItem"].Enabled = true;
                contxtMenu.DropDownItems["customStatementToolStripMenuItem"].Enabled = true;
                contxtMenu.DropDownItems["conditionToolStripMenuItem"].Enabled = false;
                contxtMenu.DropDownItems["buttonNextToolStripMenuItem"].Enabled = false;
                contxtMenu.DropDownItems["buttonCancelToolStripMenuItem"].Enabled = false;
                contxtMenu.DropDownItems["buttonPrevToolStripMenuItem"].Enabled = false;
            }
            else
            {
                contxtMenu.DropDownItems["customScriptToolStripMenuItem"].Enabled = true;
                contxtMenu.DropDownItems["initializationToolStripMenuItem"].Enabled = true;
                contxtMenu.DropDownItems["initializationCloseoutToolStripMenuItem"].Enabled = true;
                contxtMenu.DropDownItems["validationToolStripMenuItem"].Enabled = true;
                contxtMenu.DropDownItems["customStatementToolStripMenuItem"].Enabled = false;
                contxtMenu.DropDownItems["conditionToolStripMenuItem"].Enabled = true;
                contxtMenu.DropDownItems["buttonNextToolStripMenuItem"].Enabled = true;
                contxtMenu.DropDownItems["buttonCancelToolStripMenuItem"].Enabled = true;
                contxtMenu.DropDownItems["buttonPrevToolStripMenuItem"].Enabled = true;
            }


        }

        /// <summary>
        /// The AddSetting_Click event handler is called when a user clicks
        /// on any "Add" entry in the Settings context menu.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AddSetting_Click(object sender, EventArgs e)
        {
            // Get the name of the clicked element
            var setting = sender.ToString();

            AddSetting(setting);
        }

        #endregion Menu event handlers

        #endregion Event handlers

        #region Private methods


        /// <summary>
        /// The LoadConfigurationFile method loads the specified configuration file
        /// and does the initial parsing
        /// </summary>
        /// <param name="fileName"></param>
        void LoadConfigurationFile(string fileName)
        {
            wizard.Load(fileName);

            if (wizard.SelectNodes("//Pane").Count == 0)
            {
                MessageBox.Show("Invalid definition XML file selected, no panes defined.", "Invalid File",
                    MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly);
                _wizardDefinitionFile = "";
                paneTree.Nodes.Clear();
                ConfigureButtons();
                return;
            }

            _changed = false;
            _isDataReady = false;
            _wizardDefinitionFile = fileName;

            // Clear the panes dictionary
            _panes = new Dictionary<string, XmlDocument>();

            // Set the definition directory. It needs to contain all the other files and must be writable, as we'll store the 
            // temporary panes there.
            var definitionInfo = new FileInfo(_wizardDefinitionFile);
            _definitionDirectory = definitionInfo.DirectoryName;

            // Clear the tree contents
            paneTree.Nodes.Clear();

            // Add a new global node
            var globalNode = new TreeNode("Global") { Tag = wizard.SelectSingleNode("//Global") };
            paneTree.Nodes.Add(globalNode);

            // Make the panes child nodes
            foreach (XmlNode pane in wizard.SelectNodes("//Pane"))
            {
                var id = pane.Attributes["id"].Value;
                var newNode = new TreeNode(id) { Tag = pane };
                globalNode.Nodes.Add(newNode);


                // Load external references into pane dictionary if necessary
                if (pane.Attributes["reference"] == null) continue;

                // Load reference if available
                var reference = pane.Attributes["reference"].Value;
                var newPane = new XmlDocument();

                if (File.Exists(definitionInfo.DirectoryName + "\\" + reference))
                {
                    newPane.Load(definitionInfo.DirectoryName + "\\" + reference);
                }
                else
                {
                    // Create new pane file
                    CreateNewPaneFile(id, reference);
                    newPane = _panes[id];
                }


                // Ensure id attribute is in synch. Definition file takes priority
                // Try to get pane based on current ID first.
                var refPane = newPane.SelectSingleNode("/Wizard/Pane[@id='" + id + "']");

                if (refPane == null)
                {
                    // No pane with ID defined.
                    // Check if there is at least one Pane
                    refPane = newPane.SelectSingleNode("//Pane");

                    if (refPane != null)
                    {
                        // Found at least one pane. Update ID.
                        var idAttribute = newPane.CreateAttribute("id");
                        idAttribute.Value = id;
                        refPane.Attributes.SetNamedItem(idAttribute);
                    }
                }


                //if (pane.Attributes["id"].Value != newPane.SelectSingleNode("//Pane").Attributes["id"].Value)
                //    newPane.SelectSingleNode("//Pane").Attributes["id"].Value = pane.Attributes["id"].Value;


                // Synchronize conditions.
                // This Editor assumes all conditions to be duplicated 
                // in the definition file and each referenced file
                // to make testing of individual panes easier.
                // However all changes will only be applied to the 
                // referenced files during editing and copied to the
                // definition file when SaveConfiguration is called

                // Sync from definition file to reference file first
                foreach (XmlNode condition in pane.SelectNodes("Condition"))
                {
                    bool available = false;

                    foreach (XmlNode refCondition in newPane.SelectNodes("/Wizard/Pane[@id='" + id + "']/Condition"))
                    {
                        if (condition.InnerXml == refCondition.InnerXml)
                        {
                            available = true;
                            break;
                        }
                    }

                    if (!available)
                    {
                        XmlNode newCondition = newPane.CreateElement("Condition");
                        newCondition.InnerXml = condition.InnerXml;
                        newPane.SelectSingleNode("/Wizard/Pane[@id='" + id + "']").AppendChild(newCondition);
                    }
                }

                // Then from reference file to definition file
                foreach (XmlNode refCondition in newPane.SelectNodes("/Wizard/Pane[@id='" + id + "']/Condition"))
                {
                    var available = false;

                    foreach (XmlNode condition in pane.SelectNodes("Condition"))
                    {
                        if (refCondition.InnerXml == condition.InnerXml)
                        {
                            available = true;
                            break;
                        }
                    }

                    if (!available)
                    {
                        XmlNode newCondition = wizard.CreateElement("Condition");
                        newCondition.InnerXml = refCondition.InnerXml;
                        pane.AppendChild(newCondition);
                    }
                }

                // Add referenced file to panes dictionary
                // if not already added by former methods
                if (!_panes.ContainsKey(id))
                    _panes.Add(id, newPane);
            }

            // Select the global node
            paneTree.SelectedNode = paneTree.Nodes[0];
            paneTree.ExpandAll();

            ConfigureButtons();
            _isDataReady = true;
        }


        /// <summary>
        /// The SaveConfigurationFile saves all changes to the definition and all referenced files
        /// </summary>
        void SaveConfigurationFile()
        {
            // Copy all Conditions from referenced panes into the definition file
            // Eases Testing of individual panes
            foreach (XmlNode pane in wizard.SelectNodes("//Pane[@reference]"))
            {
                // Remove all current Conditions
                foreach (XmlNode condition in pane.SelectNodes("Condition"))
                    pane.RemoveChild(condition);

                var id = pane.Attributes["id"].Value;
                var reference = _panes[id];
                foreach (XmlNode refCondition in reference.SelectNodes("/Wizard/Pane[@id" + id + "]/Condition"))
                {
                    XmlNode newCondition = wizard.CreateElement("Condition");
                    newCondition.InnerXml = refCondition.InnerXml;
                    pane.AppendChild(newCondition);
                }
            }

            // Save the changes to the definition file
            wizard.Save(_wizardDefinitionFile);

            // Save changes to each referenced file
            foreach (var referencedPane in _panes.Values)
            {
                var id = referencedPane.SelectSingleNode("//Pane").Attributes["id"].Value;
                var fileName = wizard.SelectSingleNode("//Pane[@id='" + id + "']").Attributes["reference"].Value;
                referencedPane.Save(_definitionDirectory + "\\" + fileName);
            }
            _changed = false;
        }


        /// <summary>
        /// The LoadCode method populates the "Settings" tab of the form with
        /// all custom statements, initialization and validation scripts, button
        /// label changes, etc.
        /// </summary>
        void LoadCode()
        {
            var selectedPane = (XmlNode)paneTree.SelectedNode.Tag;
            XmlDocument referencedPane = null;

            _isDataReady = false;

            if (selectedPane.Name == "Global")
            {
                htmlCode.Document.Text = "";
                htmlCode.Enabled = false;
                chkNavigation.Visible = true;

                chkNavigation.Checked = selectedPane.SelectSingleNode("HideNavigation") != null;
            }
            else
            {
                // Check if current pane definition is in a referenced file
                if (selectedPane.Attributes != null)
                    if (selectedPane.Attributes["reference"] != null)
                    {
                        var referenceName = selectedPane.Attributes["reference"].Value;
                        var id = selectedPane.Attributes["id"].Value;
                        referencedPane = _panes[id];
                        selectedPane = referencedPane.SelectSingleNode("/Wizard/Pane[@id='" + id + "']");

                        // Convert Initialization and Validation Nodes in referenced global section 
                        // into CustomScript nodes for easier handling and backwards compatibility
                        // Yes, rather a hack than a solid solution and needs to be rewritten
                        var globalNode = referencedPane.SelectSingleNode("//Global");

                        if (globalNode != null)
                        {
                            globalNode.InnerXml = globalNode.InnerXml.Replace("Initialization>", "CustomScript>");
                            globalNode.InnerXml = globalNode.InnerXml.Replace("Validation>", "CustomScript>");
                        }

                        // Set referenced file information
                        chkSeparate.Checked = true;
                        chkSeparate.Enabled = false;
                        txtFilename.Text = referenceName;
                    }
                    else
                    {
                        chkSeparate.Checked = false;
                        chkSeparate.Enabled = true;
                    }

                // Set html code
                htmlCode.Enabled = true;
                if (selectedPane != null)
                {
                    htmlCode.Document.Text = selectedPane.SelectSingleNode("Body").ChildNodes[0].InnerText;

                    // Set title
                    if (selectedPane.Attributes != null)
                        txtTitle.Text = selectedPane.Attributes["title"] != null ? selectedPane.Attributes["title"].Value : "";

                    var hideBackNextNode = selectedPane.SelectSingleNode("HideBackNext");
                    if (hideBackNextNode != null && hideBackNextNode.InnerText == "true")
                    {
                        chkHideButtons.Checked = true;
                    }
                    else
                    {
                        chkHideButtons.Checked = false;
                    }
                }


                chkNavigation.Visible = false;
            }

            // Populate the list with custom statements, conditions, initialization, and validation nodes
            // Also add referenced script files if pane has a Global section
            listDetail.Items.Clear();
            if (referencedPane != null)
            {
                foreach (XmlNode customStatement in referencedPane.SelectNodes("//CustomStatement|//Condition|//Initialization|//Validation|//ButtonNext|//ButtonCancel|//InitializationCloseout|//CustomScript"))
                {
                    listDetail.Items.Add(customStatement);
                }
            }
            else
            {
                foreach (XmlNode customStatement in selectedPane.SelectNodes("CustomStatement|Condition|Initialization|Validation|ButtonNext|ButtonCancel|InitializationCloseout|CustomScript"))
                {
                    listDetail.Items.Add(customStatement);
                }
            }

            if (listDetail.Items.Count > 0)
                listDetail.SelectedItem = 0;

            _isDataReady = true;
        }


        /// <summary>
        /// The LoadPreview method takes the current pane's HTML, saves it to a file, and loads 
        /// that file into the preview pane's web control.
        /// </summary>
        void LoadPreview()
        {
            if (htmlCode.Enabled)
            {
                // Get the temporary directory to use
                var tempDir = Environment.GetEnvironmentVariable("TEMP", EnvironmentVariableTarget.Process);


                // Make sure the other required files are present in the same directory
                // Create List with all MDT files first
                var requiredFiles = new List<string>(new[]
                                                         {
                                                             "Wizard.hta", "Wizard.css", "WizUtility.vbs",
                                                             "ZTIUtility.vbs", "ZTIConfigFile.vbs",
                                                             "ZTIDataAccess.vbs", "ZTIDiskUtility.vbs",
                                                             "ZTIBCDUtility.vbs", "ZTINicUtility.vbs",
                                                             "Wizard.ico", "BackButton.JPG", "plusicon.gif",
                                                             "minusico.gif", "SelectItem.jpg",
                                                             "WelcomeBanner.jpg", "btnout.png", "btnover.png",
                                                             "btnsel.png", "BackButton.png",
                                                             "FolderIcon.png", "ItemIcon1.png", "MinusIcon1.png",
                                                             "PlusIcon1.png", "SelectItem.png",
                                                             "header-image.png", "Computer.png", "Navbar.png",
                                                         });

                // Add all files that are referenced in the html text 
                var pattern = @"(\S+)=[""']?((?:.(?![""']?\s+(?:\S+)=|[>""']))+.)[""']?";
                var refTags = new Regex(pattern, RegexOptions.None);

                foreach (Match refTag in refTags.Matches(htmlCode.Document.Text))
                {
                    if (refTag.Groups[1].Value == "src")
                    {
                        requiredFiles.Add(refTag.Groups[2].Value);
                    }
                }

                // Copy files if necessary
                foreach (var requiredFile in requiredFiles)
                {
                    if (!File.Exists(tempDir + @"\" + requiredFile))
                    {
                        var filePath = Utility.FindFile(_definitionDirectory, requiredFile);
                        if (!String.IsNullOrEmpty(filePath))
                            File.Copy(filePath, tempDir + @"\" + requiredFile, true);
                    }
                }

                // Save a temporary copy of just this pane
                File.WriteAllText(tempDir + @"\Pane.htm", "<html><head><link href=\"Wizard.css\" type=\"text/css\" rel=\"stylesheet\"><body><div id=\"MyContentArea\" class=\"ContentArea\">" + htmlCode.Document.Text + "</div></body></html>", Encoding.UTF8);

                // Show the pane in the HTML control
                webPreview.Navigate("file:" + tempDir + @"\Pane.htm");
            }
            else
            {
                webPreview.Navigate("about:blank");
            }
        }


        /// <summary>
        /// Configure the buttons appropriately, enabling them after a file has been
        /// selected and opened.
        /// </summary>
        void ConfigureButtons()
        {
            if (!String.IsNullOrEmpty(_wizardDefinitionFile.Trim()))
            {
                btnAddPane.Enabled = true;
                btnRemovePane.Enabled = true;
                btnAdd.Enabled = true;
                btnRemove.Enabled = true;
                saveToolStripMenuItem.Enabled = true;
                saveAsToolStripMenuItem.Enabled = true;
                testToolStripMenuItem.Enabled = true;
            }
            else
            {
                btnAddPane.Enabled = false;
                btnRemovePane.Enabled = false;
                btnAdd.Enabled = false;
                btnRemove.Enabled = false;
                saveToolStripMenuItem.Enabled = false;
                saveAsToolStripMenuItem.Enabled = false;
                testToolStripMenuItem.Enabled = false;
            }
        }


        /// <summary>
        /// The AddPaneToDefinition method adds a new pane to the wizard
        /// definition file, and returns the new pane as XmlNode
        /// </summary>
        /// <param name="paneID"></param>
        /// <param name="reference"></param>
        private XmlNode AddPaneToDefinition(string paneID, string reference)
        {
            var selectedNode = (XmlNode)paneTree.SelectedNode.Tag;

            // Add the new pane to the XML
            XmlNode newPane = wizard.CreateElement("Pane");

            var idAttribute = wizard.CreateAttribute("id");
            idAttribute.Value = paneID;
            newPane.Attributes.SetNamedItem(idAttribute);

            var referenceAttribute = wizard.CreateAttribute("reference");
            referenceAttribute.Value = reference;
            newPane.Attributes.SetNamedItem(referenceAttribute);


            if (selectedNode != null)
                wizard.DocumentElement.InsertAfter(newPane, selectedNode);
            else
                wizard.DocumentElement.AppendChild(newPane);


            // Add the new pane to the tree
            var newNode = new TreeNode(paneID) { Tag = newPane };

            if (selectedNode == null)
                paneTree.Nodes[0].Nodes.Add(newNode);  // Add at bottom
            else if (selectedNode.Name == "Global")
                paneTree.Nodes[0].Nodes.Insert(0, newNode);
            else
                paneTree.Nodes[0].Nodes.Insert(paneTree.SelectedNode.Index + 1, newNode);

            return newPane;
        }


        /// <summary>
        /// The CheckPaneIDUnique method checks if the supplied Pane ID is 
        /// unique. Will return true if unique, false if not.
        /// </summary>
        /// <param name="paneID"></param>
        /// <returns></returns>
        private bool CheckPaneIDUnique(String paneID)
        {
            foreach (XmlNode currentPane in wizard.SelectNodes("//Pane"))
            {
                if (currentPane.Attributes["id"].Value == paneID)
                    return false;
            }

            return true;
        }


        /// <summary>
        /// The CreateNewPaneFile method creates a new wizard pane
        /// and returns the filename
        /// </summary>
        /// <param name="paneID">Pane ID</param>
        /// <param name="fileName">proposed filename</param>
        /// <returns></returns>
        private string CreateNewPaneFile(string paneID, string fileName)
        {
            // Create base XML file
            var nl = Environment.NewLine;

            var newPaneXml = @"<Wizard>" + nl;
            newPaneXml += @"    <Global>" + nl;
            newPaneXml += @"    </Global>" + nl;
            newPaneXml += @"    <Pane id=""" + paneID + @""">" + nl;
            newPaneXml += @"        <Body>" + nl;
            newPaneXml += @"            <![CDATA[" + nl;
            newPaneXml += @"                <H1>Hello World</H1>" + nl;
            newPaneXml += @"            ]]>" + nl;
            newPaneXml += @"        </Body>" + nl;
            newPaneXml += @"    </Pane>" + nl;
            newPaneXml += @"</Wizard>";

            var newWizard = new XmlDocument();
            newWizard.LoadXml(newPaneXml);

            fileName = CreateUniqueFileName(paneID, fileName);

            newWizard.Save(_definitionDirectory + "\\" + fileName);

            _panes.Add(paneID, newWizard);
            return fileName;
        }


        /// <summary>
        /// The CopyPaneFile methods copies the referenced Pane.
        /// </summary>
        /// <param name="oldFileName"></param>
        /// <param name="newFileName"></param>
        /// <returns></returns>
        private string CopyPaneFile(string oldFileName, string newFileName)
        {
            if (Path.GetExtension(newFileName) != "xml")
                newFileName = newFileName + ".xml";

            File.Copy(_definitionDirectory + "\\" + oldFileName, _definitionDirectory + "\\" + newFileName);

            return newFileName;
        }


        /// <summary>
        /// Adds a new item to the list on the "Settings" pane.
        /// </summary>
        /// <param name="setting"></param>
        private void AddSetting(string setting)
        {
            // Get current node type
            var selectedNode = (XmlNode)paneTree.SelectedNode.Tag;

            XmlNode newNode;

            if (selectedNode.Name == "Global")
            {
                newNode = wizard.CreateElement(setting);
                selectedNode.AppendChild(newNode);

            }
            else
            {
                XmlDocument referenceNode;
                if (selectedNode.Attributes["reference"] != null)
                {
                    var id = selectedNode.Attributes["id"].Value;
                    referenceNode = _panes[id];
                    newNode = referenceNode.CreateElement(setting);

                    // In a referenced node, CustomScript has to go to referenced global section
                    if (setting == "CustomScript")
                    {
                        referenceNode.SelectSingleNode("/Wizard/Global").AppendChild(newNode);
                    }
                    else
                    {
                        // All others need to go to the referenced Pane section
                    referenceNode.SelectSingleNode("/Wizard/Pane[@id='" + id + "']").AppendChild(newNode);
                    }
                    
                // Condition will be synched to wizard definition if required during save and open
                }
                else
                {
                    newNode = wizard.CreateElement(setting);
                    selectedNode.AppendChild(newNode);
                }

                // Apply some initial formatting
                if ((newNode.Name == "Initialization") || (newNode.Name == "Validation"))
                    newNode.InnerXml = @"<![CDATA[]]>";

            }

            // Apply some additional formatting
            if ((newNode.Name == "CustomStatement") || (newNode.Name == "InitializationCloseout") || (newNode.Name == "Condition"))
                newNode.InnerXml = @"<![CDATA[]]>";
            else if (newNode.Name == "ButtonNext")
                newNode.InnerXml = @"<Label><![CDATA[<button accesskey=N id=buttonNext language=vbscript onclick=ButtonNextClick><U>N</U>ext</button>]]></Label>";
            else if (newNode.Name == "ButtonPrev")
                newNode.InnerXml = @"<Label><![CDATA[<button accesskey=B id=buttonPrev onclick=ButtonBackClick>B</U>ack</button>]]></Label>";
            else if (newNode.Name == "ButtonCancel")
                newNode.InnerXml = @"<Label><![CDATA[<button accesskey=C id=buttonCancel language=vbscript onclick=ButtonCancelClick><U>C</U>ancel</button>]]></Label>";

            // Add new item to list
            listDetail.Items.Add(newNode);

            // Select new item
            listDetail.SelectedItem = newNode;
            _changed = true;
        }


        /// <summary>
        /// The CreateUniqueFileName method creates a unique filename
        /// based on the supplied proposedFilename and/or paneID
        /// </summary>
        /// <param name="paneID"></param>
        /// <param name="proposedFilname"></param>
        /// <returns></returns>
        private string CreateUniqueFileName(string paneID, string proposedFilname)
        {
            string fileName = string.Empty;

            if (!string.IsNullOrEmpty(proposedFilname))
            {
                // Add xml extension if necessary
                if (Path.GetExtension(fileName) != "xml")
                {
                    fileName = Path.GetFileNameWithoutExtension(proposedFilname);
                }

            }
            else
            {
                // Create new filename based on Paned ID and definition name
                var baseName = Path.GetFileNameWithoutExtension(_wizardDefinitionFile).Split('_')[0];

                fileName = baseName + "_" + paneID;
            }


            // Check if file exists. If file exists, add an incrementing number 
            // to the end until filename is unique
            var testFileName = fileName;
            var id = 0;

            while (File.Exists(_definitionDirectory + "\\" + testFileName + ".xml"))
            {
                id += 1;
                testFileName = fileName + "_" + id;
            }

            fileName = testFileName;

            // Add xml extension
            fileName += ".xml";

            return fileName;
        }

        #endregion Private methods
    }

}

